# -*- coding:utf-8 -*-
"""
@author: fsksf

@since: 2022/2/23 22:49
"""
from collections import defaultdict
from datetime import datetime
from threading import Thread
from typing import Dict, List
import pandas as pd
from vcat.core.bar_generator import BarGenerator
from vcat.core.constant import (
    DataLevel,
    Exchange,
    ContractType,
    HandlerLevel,
    RunningMod,
    HandlerCategory
)
from vcat.core.event import Event, EventType
from vcat.core.event_bus import (
    event_bus_left,
    AsLeftHandler,
)
from vcat.core.obj import (
    BarData,
    BarGenArrayParams,
    TickData,
    ContractData,
)


class HistoryProvider:
    def __init__(self, main_engine, bar_callback=None, tick_callback=None, use_to_init=False):
        """

        :param main_engine:
        :param bar_callback:
        :param tick_callback:
        :param use_to_init: 用于初始化（初始化过程可以选择用分钟和日线更新）还是用于回测（回测过程只用分钟或者日线）
        """
        self._use_to_init = use_to_init
        self._main_engine: "MainEngine" = main_engine
        self.use_day_bar = main_engine.use_day_bar
        self.bar_generators: Dict[str, Dict[DataLevel, Dict[str: BarGenerator]]] = \
            defaultdict(lambda: defaultdict(dict))
        self.tick_generators: Dict[str, BarGenerator] = {}

        self._on_bar_callback = bar_callback or self._main_engine.on_bar
        self._on_tick_callback = tick_callback or self._main_engine.on_tick

    def add_rule_algo(self):
        pass

    def add_bar_generator(self, b_params: BarGenArrayParams, contract_ids: list):
        for symbol in contract_ids:
            symbol_gen = self.bar_generators[symbol][b_params.bar_level]
            if symbol not in self.tick_generators:
                self.tick_generators[symbol] = BarGenerator(on_bar=self._on_bar_callback,
                                                            symbol=symbol
                                                            )
            if b_params.bar_interval in symbol_gen:
                return
            symbol_gen[b_params.bar_interval] = BarGenerator(on_bar=self._on_bar_callback,
                                                             symbol=symbol,
                                                             window=b_params.bar_interval,
                                                             bar_level=b_params.bar_level)

    def get_bar_gen(self, cid, level, interval):
        return self.bar_generators[cid][level].get(interval)

    def get_tick_gen(self, cid):
        return self.tick_generators.get(cid)

    def gen_tick(self, tick: TickData):
        """
        利用tick数据生成 1minute分钟bar
        :param tick:
        :return:
        """

        cid_level_gen = self.get_tick_gen(tick.cid)
        if cid_level_gen is None:
            return
        cid_level_gen.update_tick(tick)

    def gen_bar(self, bar: BarData):
        if bar.interval == 1:
            self.update_level_gen(bar=bar)

    def update_level_gen(self, bar: BarData):
        """
        更新 window > 1 的bar 和 下一个级别的bar
        :param bar:
        :return:
        """
        if bar.interval != 1:
            return
        this_level = bar.level
        # 更新同级别
        for window, gen in self.bar_generators[bar.code][this_level].items():
            if window > 1:
                gen.gen_bar(bar)

        level = bar.level
        while True:
            # 跳级更新
            level = level.next()
            if level == DataLevel.day and self.use_day_bar and self._use_to_init:
                # 如果设置了 初始化过程中 日线用日线数据直接更新，并且下一个级别是日线
                return
            if level is not None:
                next_gen_dict = self.bar_generators[bar.code][level]
                if not next_gen_dict:
                    continue
                for window, gen in next_gen_dict.items():
                    # 更新临近的下一级别，结束后返回！ 不再更新下一个级别
                    if window == 1:
                        gen.gen_bar(bar)
                    return
            else:
                break

    def get_history(self, contract_ids: List[str], start_dt, end_dt, count=None, freq=DataLevel.minute):
        if end_dt is None:
            end_dt = datetime.now(tz=None)
        for hist_feeder in self._main_engine.history_feeder_instance.values():
            data: pd.DataFrame = hist_feeder.get_history(contract_ids, start_dt, end_dt, count, freq)
            data = data.sort_values(by='dt')
            if data is None or data.empty:
                continue
            return data
        return None

    def get_history_bar(self, contract_ids: List[str], start_dt, end_dt, count=None, freq=DataLevel.minute):
        data_df: pd.DataFrame = self.get_history(contract_ids=contract_ids, start_dt=start_dt, end_dt=end_dt,
                                                 count=count, freq=freq)
        if data_df is None:
            return []
        else:
            for _, item_series in data_df.iterrows():
                yield BarData(broker_name='data_feed',
                              code=item_series['code'],
                              contract_type=ContractType[item_series['contract_type']],
                              exchange=Exchange[item_series['exchange']],
                              open_price=item_series['open_price'],
                              high_price=item_series['high_price'],
                              low_price=item_series['low_price'],
                              close_price=item_series['close_price'],
                              volume=item_series['volume'],
                              dt=item_series['dt'],
                              level=freq,
                              interval=1
                              )

    def tick_bc(self, tick: TickData):
        self.gen_tick(tick)
        self._on_tick_callback(tick)

    def bar_bc(self, bar: BarData):
        if bar.interval == 1:
            self.gen_bar(bar)
        self._on_bar_callback(bar)

    def change_to_backtest(self):
        self._use_to_init = False

    def change_to_init(self):
        self._use_to_init = True

    def change_callback(self, bar_callback, tick_callback):
        self._on_bar_callback = bar_callback
        self._on_tick_callback = tick_callback

    def __bool__(self):
        return bool(self.tick_generators) or bool(self.bar_generators)


class GuardLeft(HistoryProvider):
    """
    信号守卫
    行情数据中心
    负责行情过滤、合成、计算
    """

    def __init__(self, main_engine, bar_callback=None, tick_callback=None, use_to_init=False):
        super(GuardLeft, self).__init__(main_engine, bar_callback, tick_callback, use_to_init)
        self.event_bus = event_bus_left
        self.register()

    def register(self):
        self.event_bus.as_handler(category=HandlerCategory.EVENT,
                                  event_type=EventType.BAR,
                                  level=HandlerLevel.ON_MAIN_HANDLER)(self.bar_event_handler)

        self.event_bus.as_handler(category=HandlerCategory.EVENT,
                                  event_type=EventType.TICK,
                                  level=HandlerLevel.ON_MAIN_HANDLER)(self.tick_event_handler)

    def put(self, event: Event):
        self.event_bus.put(event)

    def put_log(self, msg):
        self.event_bus.put_log(msg)

    def put_tick(self, tick: TickData):
        self.event_bus.put_tick(tick)

    def put_bar(self, bar: BarData):
        self.event_bus.put_bar(bar)

    def put_stop_signal(self):
        self.event_bus.put_stop_single()

    def subscribe(self, contract_ids: List[str]):

        contract_dict: Dict[str, List[ContractData]] = defaultdict(list)
        for contract_id in contract_ids:
            contract: ContractData = self._main_engine.get_contract(contract_id)
            contract_dict[contract.broker_name].append(contract)
        for broker_name, contract_list in contract_dict.items():
            broker = self._main_engine.get_broker(broker_name)
            broker.subscribe(contract_list)

    def feed_events(self, contract_ids: List[str], start_dt, end_dt, freq=DataLevel.minute):
        """模拟实盘事件"""
        def func():
            for bar in self.get_history_bar(contract_ids=contract_ids,
                                            start_dt=start_dt,
                                            end_dt=end_dt,
                                            freq=freq):
                self.put_bar(bar)
            self.put_stop_signal()

        th = Thread(target=func)
        th.start()
        return

    def tick_event_handler(self, tick_event: Event):
        """
        Tick事件处理函数、！！！ 回测需屏蔽
        """
        tick: TickData = tick_event.data
        self.tick_bc(tick)

    def bar_event_handler(self, bar_event: Event):
        """
        Bar事件处理函数、！！！ 回测需屏蔽
        """
        bar: BarData = bar_event.data
        self.bar_bc(bar)

    def set_up(self):
        self.event_bus.set_up()

    def tear_down(self):
        self.event_bus.tear_down()

    def join(self):
        self.event_bus.join()

    def __bool__(self):
        return bool(self.tick_generators) or bool(self.bar_generators)


if __name__ == '__main__':
    from vcat.core.engine import MainEngine